package com.lxk.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.lxk.dao.ConsumeOrderMapper;
import com.lxk.dao.DeviceMapper;
import com.lxk.dao.RulesMapper;
import com.lxk.dao.UsersMapper;
import com.lxk.pojo.ConsumeOrder;
import com.lxk.pojo.Device;
import com.lxk.pojo.Rules;
import com.lxk.pojo.Users;
import com.lxk.redis.RedisKey;
import com.lxk.redis.RedisUtil;
import com.lxk.service.PlayService;
import com.lxk.util.QRCodeGenerator;
import com.lxk.util.exception.GlobalException;
import com.lxk.vo.CurrentRedisVO;
import com.lxk.vo.ScanResultVO;
import com.lxk.vo.result.CodeMsg;
import com.lxk.vo.QueueUserVO;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.nio.charset.StandardCharsets;
import java.util.*;

/**
 * @author JohnSoybean
 * @create 2020/7/15 10:40
 * @description
 */
@Slf4j
@Service
public class PlayServiceImpl implements PlayService {

    @Autowired
    private RedisUtil redisUtil;

    @Autowired
    private UsersMapper usersMapper;
    @Autowired
    private DeviceMapper deviceMapper;
    @Autowired
    private RulesMapper rulesMapper;
    @Autowired
    private ConsumeOrderMapper consumeOrderMapper;

    @Override
    public Device deviceVerify(String deviceNumber) {
        List<Device> devices = deviceMapper.selectList(new LambdaQueryWrapper<Device>().eq(Device::getDeviceNumber, deviceNumber));
        if (devices.size() > 0) {
            return devices.get(0);
        } else {
            return null;
        }
    }

    @Override
    public String getQRCode(Long userId) {
        Users users = usersMapper.selectById(userId);
        String qrcode = users.getQrcode();
        //判断是否被冻结，被冻结者直接返回空
        if (users.getBlock() == 1) {
            throw new GlobalException(CodeMsg.ACCOUNT_BLOCKED);
        }
        String uuid = users.getUuid();
        if (null == qrcode || qrcode.trim().equals("") || !QRCodeGenerator.isQrcodeExist(qrcode)) {
            //不存在二维码，判断是否存在uuid
            if (null == uuid || uuid.trim().equals("")) {   //不存在，生成uuid
                uuid = String.valueOf(userId) + System.currentTimeMillis();
            }
            //生成二维码
            try {
                qrcode = QRCodeGenerator.generateQRCodeImage(uuid, 500, 500);
            } catch (Exception e) {
                e.printStackTrace();
                throw new GlobalException(CodeMsg.QRCODE_INIT_ERR);
            }
            //更新数据库
            Users updUsers = new Users();
            updUsers.setUserId(userId);
            updUsers.setQrcode(qrcode);
            updUsers.setUuid(uuid);
            usersMapper.updateById(updUsers);
        }
        //已存在，更新二维码过期时间及二维码缓存信息
        ObjectMapper om = new ObjectMapper();
        ScanResultVO vo = new ScanResultVO();
        vo.setStatus(0);
        vo.setUserId(userId);
        try {
            String json = om.writeValueAsString(vo);
            String key = redisUtil.joinKey(RedisKey.QRCODE.getPrefix(), uuid);
            redisUtil.set(key, json, RedisKey.QRCODE.getExpire());
            //返回二维码路径
            return qrcode;
        } catch (JsonProcessingException e) {
            e.printStackTrace();
            throw new GlobalException(CodeMsg.QRCODE_INIT_ERR);
        }
    }

    @Override
    public String byteArrayToUuid(byte[] bytes) {
        String uuid = new String(bytes, StandardCharsets.ISO_8859_1);
        return uuid;
    }

    @Override
    public Long uuidToUserId(String uuid) {
        String key = redisUtil.joinKey(RedisKey.QRCODE.getPrefix(), uuid);
        String value = (String)redisUtil.get(key);
        if (value == null) {
            //不存在或已过期
            return null;
        }else {
            //存在且未过期
            ObjectMapper om = new ObjectMapper();
            try {
                ScanResultVO vo = om.readValue(value, ScanResultVO.class);
                Long userId = vo.getUserId();
                return userId;
            } catch (JsonProcessingException e) {
                e.printStackTrace();
                return null;
            }
        }
    }

    @Override
    public boolean isQrCodeActivated(Long userId) {
        Users users = usersMapper.selectById(userId);
        String uuid = users.getUuid();
        if (null == uuid || uuid.trim().equals("")) {   //不存在uuid
            return false;
        }
        String key = redisUtil.joinKey(RedisKey.QRCODE.getPrefix(), uuid);
        return redisUtil.hasKey(key);
    }

    @Override
    public boolean joinQueue(Device device, Long userId, String uuid) {
        ObjectMapper om = new ObjectMapper();
        //判断current是否存在 判断是否是空队列（第一个排队，且没有正在进行，重设过期时间）
        String key = redisUtil.joinKey(RedisKey.QUEUE.getPrefix(), device.getDeviceNumber());
        String currentKey = redisUtil.joinKey(RedisKey.CURRENT.getPrefix(), device.getDeviceNumber());
        String qrcodeKey = redisUtil.joinKey(RedisKey.QRCODE.getPrefix(), uuid);
        if (redisUtil.hasKey(key)) {
            //判断是否已在队列中
            List<Object> queue = redisUtil.lGet(key, 0, -1);
            if (queue != null) {
                boolean isIn = false;
                for (Object obj : queue) {
                    Long queueUserId = getUserIdFromJson((String) obj);
//                    long queueUserId = Long.parseLong(obj.toString());
                    if (userId.equals(queueUserId)) {
                        isIn = true;
                        break;
                    }
                }
                if (isIn) {
                    log.info("已在队列中 - 设备: "+device.getDeviceNumber()+", 用户: "+userId);
                    //轮询数据 已在队列中
                    ScanResultVO vo = new ScanResultVO();
                    vo.setStatus(2);
                    vo.setType(1);
                    vo.setErrMsg("您已在队列中");
                    vo.setUserId(userId);
                    vo.setDeviceName(device.getDeviceName());
                    vo.setQueuePosition(null);
                    try {
                        String json = om.writeValueAsString(vo);
                        redisUtil.set(qrcodeKey, json, RedisKey.QRCODE.getExpire());
                    } catch (JsonProcessingException e) {
                        e.printStackTrace();
                    }
                    return false;
                }
            }
        } else {
            boolean flag = false;
            String json = (String) redisUtil.get(currentKey);
            CurrentRedisVO current = null;
            if (null != json) {
                try {
                    current = om.readValue(json, CurrentRedisVO.class);
                    if (current != null) {
                        if (null != current.getStatus() && current.getStatus() == 1) {
                            flag = true;    //有人正在进行
                        }
                    }
                } catch (JsonProcessingException e) {
                    e.printStackTrace();
                }
            }
            if (!flag) {    //无人正在进行
                current = new CurrentRedisVO(0, null, System.currentTimeMillis());
                String jsonString = null;
                try {
                    jsonString = om.writeValueAsString(current);
                } catch (JsonProcessingException e) {
                    e.printStackTrace();
                }
                redisUtil.set(currentKey, jsonString, RedisKey.CURRENT.getExpire());
            }
        }
        //加入队列
        Long index = addUserToQueue(key, userId);
        if (null == index) {
            //轮询数据 加入失败
            ScanResultVO vo = new ScanResultVO();
            vo.setStatus(2);
            vo.setType(1);
            vo.setErrMsg("加入队列失败");
            vo.setUserId(userId);
            vo.setDeviceName(device.getDeviceName());
            vo.setQueuePosition(null);
            try {
                String json = om.writeValueAsString(vo);
                redisUtil.set(qrcodeKey, json, RedisKey.QRCODE.getExpire());
            } catch (JsonProcessingException e) {
                e.printStackTrace();
            }
            return false;
        } else {
            //轮询数据 加入成功
            ScanResultVO vo = new ScanResultVO();
            vo.setStatus(1);
            vo.setType(1);
            vo.setErrMsg(null);
            vo.setUserId(userId);
            vo.setDeviceName(device.getDeviceName());
            vo.setQueuePosition(index.intValue());
            try {
                String json = om.writeValueAsString(vo);
                redisUtil.set(qrcodeKey, json, RedisKey.QRCODE.getExpire());
            } catch (JsonProcessingException e) {
                e.printStackTrace();
            }
            return true;
        }
    }

    @Override
    public Integer confirmScan(Device device, Long userId, String uuid) {
        String queueKey = redisUtil.joinKey(RedisKey.QUEUE.getPrefix(), device.getDeviceNumber());
        String currentKey = redisUtil.joinKey(RedisKey.CURRENT.getPrefix(), device.getDeviceNumber());
        String qrcodeKey = redisUtil.joinKey(RedisKey.QRCODE.getPrefix(), uuid);
        ObjectMapper om = new ObjectMapper();
        //判断当前是否有人在玩
        String json = (String) redisUtil.get(currentKey);
        try {
            if (json != null) {
                CurrentRedisVO vo = om.readValue(json, CurrentRedisVO.class);
                System.out.println(vo);
                if (vo != null) {
                    if (vo.getStatus() == 1) {
                        log.info("前一位用户未结束");
                        //轮询数据 前一位用户未结束
                        ScanResultVO resVo = new ScanResultVO();
                        resVo.setStatus(2);
                        resVo.setType(2);
                        resVo.setErrMsg("前一位用户未结束");
                        resVo.setUserId(userId);
                        resVo.setDeviceName(device.getDeviceName());
                        resVo.setQueuePosition(null);
                        try {
                            String jsonString = om.writeValueAsString(resVo);
                            redisUtil.set(qrcodeKey, jsonString, RedisKey.QRCODE.getExpire());
                        } catch (JsonProcessingException e) {
                            e.printStackTrace();
                        }
                        return 0;
                    }
                }
            }
        } catch (JsonProcessingException e) {
            e.printStackTrace();
        }
        //判断是否排到
        Object object = redisUtil.lGetIndex(queueKey, 0);
        if (object == null) {
            log.info("无队列");
            //轮询数据 无队列
            ScanResultVO vo = new ScanResultVO();
            vo.setStatus(2);
            vo.setType(2);
            vo.setErrMsg("您不在队列中或已超时");
            vo.setUserId(userId);
            vo.setDeviceName(device.getDeviceName());
            vo.setQueuePosition(null);
            try {
                String jsonString = om.writeValueAsString(vo);
                redisUtil.set(qrcodeKey, jsonString, RedisKey.QRCODE.getExpire());
            } catch (JsonProcessingException e) {
                e.printStackTrace();
            }
            return 0;
        }
        Long firstUserId = getUserIdFromJson((String) object);
        if (!userId.equals(firstUserId)) {
            log.info("未轮到用户" + userId);
            //轮询数据 未轮到
            ScanResultVO vo = new ScanResultVO();
            vo.setStatus(2);
            vo.setType(2);
            vo.setErrMsg("还未轮到您，或不在队列中");
            vo.setUserId(userId);
            vo.setDeviceName(device.getDeviceName());
            vo.setQueuePosition(null);
            try {
                String jsonString = om.writeValueAsString(vo);
                redisUtil.set(qrcodeKey, jsonString, RedisKey.QRCODE.getExpire());
            } catch (JsonProcessingException e) {
                e.printStackTrace();
            }
            return 0;
        }
        //获取银币 发球数
        Rules rules = getRule();
        Integer ball = rules.getRuleBall();
        Integer cost = rules.getRuleCost();
        //扣银币
        Integer decrCoins = usersMapper.decrCoins(userId, cost);
        if (decrCoins <= 0) {
            log.info("银币余额不足");
            //轮询数据 银币余额不足
            ScanResultVO vo = new ScanResultVO();
            vo.setStatus(2);
            vo.setType(2);
            vo.setErrMsg("银币余额不足");
            vo.setUserId(userId);
            vo.setDeviceName(device.getDeviceName());
            vo.setQueuePosition(null);
            try {
                String jsonString = om.writeValueAsString(vo);
                redisUtil.set(qrcodeKey, jsonString, RedisKey.QRCODE.getExpire());
            } catch (JsonProcessingException e) {
                e.printStackTrace();
            }
            return 0;
        }
        //redis操作 修改当前玩家 记录游戏开始时间 删除队列顶部
        CurrentRedisVO current = new CurrentRedisVO(1, userId, System.currentTimeMillis());
        try {
            String jsonString = om.writeValueAsString(current);
            redisUtil.set(currentKey, jsonString, RedisKey.CURRENT.getExpire());
        } catch (JsonProcessingException e) {
            e.printStackTrace();
        }
        redisUtil.listRemoveFirst(queueKey);
        //记录游戏
        ConsumeOrder order = new ConsumeOrder();
        order.setConsumeBall(ball);
        order.setConsumePrice(cost);
        order.setDeviceId(device.getDeviceId());
        order.setDeviceName(device.getDeviceName());
        order.setUserId(userId);
        consumeOrderMapper.insert(order);
        //轮询数据 扫码确认成功
        ScanResultVO vo = new ScanResultVO();
        vo.setStatus(1);
        vo.setType(2);
        vo.setErrMsg("扫码确认成功，请开始游戏");
        vo.setUserId(userId);
        vo.setDeviceName(device.getDeviceName());
        vo.setQueuePosition(null);
        try {
            String jsonString = om.writeValueAsString(vo);
            redisUtil.set(qrcodeKey, jsonString, RedisKey.QRCODE.getExpire());
        } catch (JsonProcessingException e) {
            e.printStackTrace();
        }
        return ball;
    }

    @Override
    public boolean endCurrent(String deviceNumber, Long userId) {
        String currentKey = redisUtil.joinKey(RedisKey.CURRENT.getPrefix(), deviceNumber);
        ObjectMapper om = new ObjectMapper();
        //判断当前玩家是否为上报结束的玩家
        String json = (String) redisUtil.get(currentKey);
        try {
            if (json != null) {
                CurrentRedisVO vo = om.readValue(json, CurrentRedisVO.class);
                if (vo != null) {
                    if (!userId.equals(vo.getUserId())) {
                        log.info("结束的不是同一位用户");
                        return false;
                    }
                }
            }
            //更改为结束状态
            CurrentRedisVO current = new CurrentRedisVO(0, null, null);
            String jsonString = om.writeValueAsString(current);
            redisUtil.set(currentKey, jsonString, RedisKey.CURRENT.getExpire());
            return true;
        } catch (JsonProcessingException e) {
            e.printStackTrace();
            return false;
        }

    }

    @Override
    public void scheduleQueueExpire() {
        Rules rules = getRule();
        Long ruleWait = rules.getRuleWait() * 1000L;
        Long ruleExpire = rules.getRuleExpire() * 1000L;
        Long nowMillis = System.currentTimeMillis();
        ObjectMapper om = new ObjectMapper();
        String prefix = RedisKey.QUEUE.getPrefix() + "*";
        Set<String> queueKeys = redisUtil.getKeys(prefix);
        for (String queueKey : queueKeys) {
            String currentKey = queueKey.replace(RedisKey.QUEUE.getPrefix(), RedisKey.CURRENT.getPrefix());
            String json = (String) redisUtil.get(currentKey);
            try {
                CurrentRedisVO current = null;
                if (null != json) {
                    current = om.readValue(json, CurrentRedisVO.class);
                }
                if (null != current) {   //redis缓存存在
                    Integer status = current.getStatus();
                    Long begin = current.getBegin();
                    if (status == 0) {  //该机器无正在游戏，判断排队是否超时
                        if (begin == null) {    //过期起始时间为空：设置时间
                            current.setBegin(nowMillis);
                            String jsonString = om.writeValueAsString(current);
                            redisUtil.set(currentKey, jsonString, RedisKey.CURRENT.getExpire());
                            log.info(currentKey + "已结束，设置过期时间");
                        } else {                //过期起始时间存在：计算是否超时
                            if (begin + ruleWait < nowMillis) {   //已超时：删去队列顶，重设开始时间为当前
                                redisUtil.listRemoveFirst(queueKey);
                                current.setBegin(nowMillis);
                                String jsonString = om.writeValueAsString(current);
                                redisUtil.set(currentKey, jsonString, RedisKey.CURRENT.getExpire());
                                log.info(queueKey + "超时，删去队列顶");
                            }
                        }
                    } else {    //有正在游戏，判断游戏是否超时
                        if (begin == null) {    //游戏起始时间为空：设置时间
                            current.setBegin(nowMillis);
                            String jsonString = om.writeValueAsString(current);
                            redisUtil.set(currentKey, jsonString, RedisKey.CURRENT.getExpire());
                            log.info(currentKey + "正在游戏中，设置过期时间");
                        } else {                //游戏起始时间存在：计算是否超时
                            if (begin + ruleExpire < nowMillis) {   //已超时：重设当前玩家
                                current.setStatus(0);
                                current.setUserId(null);
                                current.setBegin(nowMillis);
                                String jsonString = om.writeValueAsString(current);
                                redisUtil.set(currentKey, jsonString, RedisKey.CURRENT.getExpire());
                                log.info(queueKey + "正在游戏玩家已超时，强制结束");
                            }
                        }
                    }
                } else {            //redis缓存不存在
                    current = new CurrentRedisVO(0, null, nowMillis);
                    String jsonString = om.writeValueAsString(current);
                    redisUtil.set(currentKey, jsonString, RedisKey.CURRENT.getExpire());
                    log.info(queueKey + "不存在，重新设置");
                }
            } catch (JsonProcessingException e) {
                e.printStackTrace();
            }
        }
    }

    @Override
    public Rules getRule() {
        ObjectMapper om = new ObjectMapper();
        String json = (String) redisUtil.get(RedisKey.RULES.getPrefix());
        Rules rules = null;
        if (null != json) {
            try {
                rules = om.readValue(json, Rules.class);
            } catch (JsonProcessingException e) {
                e.printStackTrace();
            }
        }
        if (rules == null) {
            rules = rulesMapper.selectById(1L);
            try {
                String jsonString = om.writeValueAsString(rules);
                redisUtil.set(RedisKey.RULES.getPrefix(), jsonString, RedisKey.RULES.getExpire());
            } catch (JsonProcessingException e) {
                e.printStackTrace();
            }
        }
        return rules;
    }

    /**
     * 加入队列
     * @param key
     * @param userId
     * @return
     */
    private Long addUserToQueue(String key, Long userId) {
        Users users = usersMapper.selectById(userId);
        if (users == null) {
            return null;
        }
        QueueUserVO vo = new QueueUserVO();
        vo.setUserId(userId);
        vo.setUsername(users.getUsername());
        vo.setHeadimg(users.getHeadimg());
        ObjectMapper om = new ObjectMapper();
        try {
            String json = om.writeValueAsString(vo);
            return redisUtil.listAddLast(key, json, RedisKey.QUEUE.getExpire());
        } catch (JsonProcessingException e) {
            e.printStackTrace();
            return null;
        }
    }

    /**
     * 从json字符串中获取用户id
     * @param json
     * @return
     */
    private Long getUserIdFromJson(String json) {
        if (json == null) {
            return null;
        }
        ObjectMapper om = new ObjectMapper();
        try {
            QueueUserVO vo = om.readValue(json, QueueUserVO.class);
            if (vo != null) {
                return vo.getUserId();
            } else {
                return null;
            }
        } catch (JsonProcessingException e) {
            e.printStackTrace();
            return null;
        }
    }

}
